package com.ochnas.volunteersignup.ui;

import static com.ochnas.volunteersignup.ui.SharedConstants.SIMPLE_DATE_FORMAT;
import static com.ochnas.volunteersignup.ui.SharedConstants.SIMPLE_DATE_TIME_FORMAT;
import static com.ochnas.volunteersignup.ui.SharedConstants.SIMPLE_TIME_PATTERN;
import static com.ochnas.volunteersignup.ui.SharedConstants.SIMPLE_TIME_ZONE_FORMAT;

import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import com.ochnas.volunteersignup.domain.Event;
import com.ochnas.volunteersignup.domain.FilledVolunteerRequisition;
import com.ochnas.volunteersignup.domain.Position;
import com.ochnas.volunteersignup.domain.TimeSlot;
import com.ochnas.volunteersignup.domain.User;
import com.ochnas.volunteersignup.service.EventService;
import com.ochnas.volunteersignup.service.UserService;
import com.ochnas.volunteersignup.service.VolunteerService;

public class EventController {
	private ApplicationService applicationService;
	private VolunteerService volunteerService;
	private UserService userService;
	private EventService eventService;
	private User user;
	private Event event;
	private String currTimeZone;
	// mapOfPositionVolunteers.key = position id
	private SortedMap<Long, List<FilledVolunteerRequisition>> mapOfPositionVolunteers;
	// mapOfTimeSlotVolunteers.key = timeSlot.id
	private SortedMap<Long, List<FilledVolunteerRequisition>> mapOfTimeSlotVolunteers;
	private Position position;
	private TimeSlot timeSlot;
	
	public EventController() {
		super();
		user = new User();
		event = new Event();
		position = new Position();
		timeSlot = new TimeSlot ();	
		mapOfPositionVolunteers = new TreeMap<Long, List<FilledVolunteerRequisition>>();
		mapOfTimeSlotVolunteers = new TreeMap<Long, List<FilledVolunteerRequisition>>();
		Calendar calendar = Calendar.getInstance();
		calendar.setTimeInMillis(System.currentTimeMillis());
		currTimeZone = "("+SIMPLE_TIME_ZONE_FORMAT.format(calendar.getTime()) + ")";
	}
	
	/**
	 * To create a new event
	 * @return newEvent.jsp
	 */
	public String newEvent() {
		event = new Event();
		String userId = applicationService.getRequestParameter("userId");
		try {
			user = userService.findUserAccount(Long.parseLong(userId));
			event.setCoordinator(user);
		} catch (NumberFormatException e) {
			applicationService.getRequestParameter(e.getMessage());
			return null;
		}
		
		return "newEvent";
	}
	
	public String createPosition () {
		
		position = new Position();
		String eventId = applicationService.getRequestParameter("eventId");
		event = eventService.findEvent(Long.parseLong(eventId));
		return "createPosition";
	}
	public String cancelPosition() {
		position = new Position();
		return "editEventDetail";
	}
	
	public String savePosition () {
		
		boolean isValid = true;
		
		if(position.getName() == null || "".equals(position.getName())) {
			applicationService.addErrorMessage("createPositionForm:positionName",
											  "Please enter name.");
			isValid = false;
		}
		
		if(position.getRequiredNumberOfVolunteers() < 1) {
			applicationService.addErrorMessage("createPositionForm:requiredNumVolunteers", 
											  "Please enter required number of volunteers.");
			isValid = false;
		}
		
		if (isValid) {
			try {
				eventService.createPositionForEvent(event, position.getName(), position.getDescription(),
																	position.getRequiredNumberOfVolunteers());
				event = eventService.findEvent(event.getId());
			} catch (Exception e) {
				applicationService.addErrorMessage("createPositionForm:summary", e.getMessage());
				return null;
			}
		} else {
			return null;
		}
		
		applicationService.addErrorMessage("editEventDetailForm:summary",
				"Position created successfully for Event: " + event.getName());
		return "editEventDetail";
		
	} // end of savePosition method	
	
	public String createTimeSlot () {
		
		timeSlot = new TimeSlot ();
		String positionId = applicationService.getRequestParameter("positionId");
		position = eventService.findPosition(Long.parseLong(positionId));
		return "createTimeSlot";
	}

	public String cancelTimeSlot() {
		timeSlot = new TimeSlot ();
		return "editEventDetail";
	}

	public String saveTimeSlot () {
		Calendar calendar = Calendar.getInstance();
		boolean isValid = true;
		boolean isTimeOkay = true;
		if(timeSlot.getStartTime() == null || "".equals(timeSlot.getStartTime())) {
			applicationService.addErrorMessage("createTimeSlotForm:startTimeSlot",
			  									"Please enter Start Time.");
			isValid = false;
			isTimeOkay = false;
		} else {  
			if(!SIMPLE_TIME_PATTERN.matcher(timeSlot.getStartTime().toString()).matches()) {
				applicationService.addErrorMessage("createTimeSlotForm:startTimeSlot",
													"Please enter a valid Start Time format.");
				   isValid = false;
				   isTimeOkay = false;
			}	
		}
		
		if(timeSlot.getEndTime() == null || "".equals(timeSlot.getEndTime())) {
			applicationService.addErrorMessage("createTimeSlotForm:endTimeSlot",
												"Please enter End Time.");
			isValid = false;
			isTimeOkay = false;
		} else {
			if(!SIMPLE_TIME_PATTERN.matcher(timeSlot.getEndTime().toString()).matches()) {
				applicationService.addErrorMessage("createTimeSlotForm:endTimeSlot", "Please enter a valid End Time format.");
				isValid = false;
				isTimeOkay = false;
			}
		}
		if (isTimeOkay) {
			calendar.set(Calendar.HOUR_OF_DAY, timeSlot.getStartTime().getHourOfDay());
			calendar.set(Calendar.MINUTE, timeSlot.getStartTime().getMinutes());
			Date sDateTime = calendar.getTime();
			calendar.set(Calendar.HOUR_OF_DAY, timeSlot.getEndTime().getHourOfDay());
			calendar.set(Calendar.MINUTE, timeSlot.getEndTime().getMinutes());
			Date eDateTime = calendar.getTime();
			isTimeOkay = sDateTime.before(eDateTime);
			if (!isTimeOkay) {
				applicationService.addErrorMessage("createTimeSlotForm:startTimeSlot", "Invalid start time. Enter a start time that occurs before the end time.");
				isValid = false;
			} 
			if (isTimeOkay) {
				calendar.set(Calendar.HOUR_OF_DAY, event.getStartTime().getHourOfDay());
				calendar.set(Calendar.MINUTE, event.getStartTime().getMinutes());
				Date eventStartDateTime = calendar.getTime();
				calendar.set(Calendar.HOUR_OF_DAY, event.getEndTime().getHourOfDay());
				calendar.set(Calendar.MINUTE, event.getEndTime().getMinutes());
				Date eventEndDateTime = calendar.getTime();
				// check if within event start time and end time
				if (sDateTime.before(eventStartDateTime) || sDateTime.after(eventEndDateTime)) {
					applicationService.addErrorMessage("createTimeSlotForm:startTimeSlot", "Invalid start time. Start time occurs outside the event from " +event.getStartTime() + " to " + event.getEndTime());
					isValid = false;
					isTimeOkay = false;
				}
				if (eDateTime.before(eventStartDateTime) || sDateTime.after(eventEndDateTime)) {
					applicationService.addErrorMessage("createTimeSlotForm:endTimeSlot", "Invalid end time. End time occurs outside the event from " +event.getStartTime() + " to " + event.getEndTime());
					isValid = false;
					isTimeOkay = false;
				}				
				if (isTimeOkay) {
					// check for overlaps with other time slots in position
					List<TimeSlot> cTimeSlots = volunteerService.findTimeSlotsByPosition(position.getId());
					for (TimeSlot ct: cTimeSlots) {
						calendar.set(Calendar.HOUR_OF_DAY, ct.getStartTime().getHourOfDay());
						calendar.set(Calendar.MINUTE, ct.getStartTime().getMinutes());
						Date ctStartDateTime = calendar.getTime();
						calendar.set(Calendar.HOUR_OF_DAY, ct.getEndTime().getHourOfDay());
						calendar.set(Calendar.MINUTE, ct.getEndTime().getMinutes());
						Date ctEndDateTime = calendar.getTime();
				
						// check start time
						if (sDateTime.equals(ctStartDateTime)) {
							applicationService.addErrorMessage("createTimeSlotForm:startTimeSlot", "Invalid start time. Start time overlaps with another time slot: " + ct.getStartTime() + " to " +ct.getEndTime());
							isTimeOkay = false;
						
						}
						
						if (sDateTime.after(ctStartDateTime) && sDateTime.before(ctEndDateTime)) {
							applicationService.addErrorMessage("createTimeSlotForm:startTimeSlot", "Invalid start time. Start time overlaps with another time slot: " + ct.getStartTime() + " to " +ct.getEndTime());
							isTimeOkay = false;
					
						}
						// check end time
						if (eDateTime.after(ctStartDateTime) && eDateTime.before(ctEndDateTime)) {
							applicationService.addErrorMessage("createTimeSlotForm:endTimeSlot", "Invalid end time. End time overlaps with another time slot: " + ct.getStartTime() + " to " +ct.getEndTime());
							isTimeOkay = false;
						}
						
						
						if (!isTimeOkay) {
							isValid = false;
							break;
						}
					}
				}
			}
		}
		
		if(timeSlot.getQuantityOfVolunteers() < 1) {
			applicationService.addErrorMessage("createTimeSlotForm:qOfVolunteers", 
												"Please enter the quantity of volunteers.");
			isValid = false;
			
		}
		

		
		if (isValid) {
			try {
				eventService.createTimeSlotForPosition(position, timeSlot.getStartTime(), timeSlot.getEndTime(), timeSlot.getQuantityOfVolunteers());
				event = eventService.findEvent(event.getId());
			} catch (Exception e) {
				applicationService.addErrorMessage("createTimeSlotForm:summary", e.getMessage());
				return null;
			}
		} else {
			return null;
		}
		
		applicationService.addErrorMessage("editEventDetailForm:summary",
				"Position created successfully for Position: " + position.getName());
		return "editEventDetail";
				
	} // end of saveTimeSlot method
	
	/**
	 * Edit the base information of the event: name, description, start date, end date and times.
	 * @return The newEvent.jsp 
	 */
	public String editEventInformation() {
		String userId = applicationService.getRequestParameter("userId");
		String eventId = applicationService.getRequestParameter("eventId");
		try {
			user = userService.findUserAccount(Long.parseLong(userId));
			event = eventService.findEvent(Long.parseLong(eventId));
		} catch (NumberFormatException e) {
			applicationService.addErrorMessage(e.getMessage());
			return null;
		}
		return "newEvent"; 
	}
	
	/**
	 * Edit the event detail by display edit option for base information, position options and time slot options
	 * @return editEvent.jsp
	 */
	public String editEventDetail() {
		String eventId = applicationService.getRequestParameter("eventId");
		String userId = applicationService.getRequestParameter("userId");
		try {
			user = userService.findUserAccount(Long.parseLong(userId));
			event = eventService.findEvent(Long.parseLong(eventId));
			// this map contains volunteers for positions without time slots
			mapOfPositionVolunteers.putAll(volunteerService.findVolunteersForPositionsWithoutTimeSlotsByEvent(event.getId()));
			// this map contains volunteers for time slots
			mapOfTimeSlotVolunteers.putAll(volunteerService.findVolunteersForTimeSlotsByEvent(event.getId()));
			
		} catch (NumberFormatException e) {
			applicationService.addErrorMessage(e.getMessage());
			return null;
		}
		
		return "editEventDetail";
	}	
	
	/**
	 * Performs refresh of event detail
	 */
	private void refreshEventDetail() {
		event = eventService.findEvent(event.getId());
		// this map contains volunteers for positions without time slots
		mapOfPositionVolunteers.putAll(volunteerService.findVolunteersForPositionsWithoutTimeSlotsByEvent(event.getId()));
		// this map contains volunteers for time slots
		mapOfTimeSlotVolunteers.putAll(volunteerService.findVolunteersForTimeSlotsByEvent(event.getId()));
	}
	
	/**
	 * Creates new or saves modified event base information
	 * @return myEvents.jsp for after creating or editEvent.jsp after modifying
	 */
	public String saveEvent() {
		Calendar calendar = Calendar.getInstance();
		boolean isValid = true;
		boolean dateValuesOkay = true;
		boolean timeValuesOkay = true;
		if (event.getName() == null || "".equals(event.getName())) {
			applicationService.addErrorMessage("eventForm:name", "Please enter Event name.");
			isValid = false;			
		}
		
		if (event.getStartDate() == null) {
			applicationService.addErrorMessage("eventForm:startDate", "Please enter start date.");
			isValid = false;
			dateValuesOkay = false;
		}  else {
			calendar.setTimeInMillis(System.currentTimeMillis());
			Date currDateTime =  calendar.getTime();
			if (event.getStartDate().before(currDateTime)) {
				applicationService.addErrorMessage("eventForm:startDate", "Please enter a start date that occurs after " + SIMPLE_DATE_FORMAT.format(currDateTime));
				dateValuesOkay = false;
				isValid = false;
			}
		}
		
		if (event.getEndDate() == null) {
			applicationService.addErrorMessage("eventForm:endDate", "Please enter end date.");
			isValid = false;
			dateValuesOkay = false;
		} else {
			calendar.setTimeInMillis(System.currentTimeMillis());
			Date currDateTime =  calendar.getTime();
			if (event.getEndDate().before(currDateTime)) {
				applicationService.addErrorMessage("eventForm:endDate", "Please enter an end date that occurs after " + SIMPLE_DATE_FORMAT.format(currDateTime));
				dateValuesOkay = false;
				isValid = false;
			}
		}
		
		if (dateValuesOkay) {
			dateValuesOkay = (event.getStartDate().before(event.getEndDate()) ||
					event.getStartDate().equals(event.getEndDate()));
					
			if (!dateValuesOkay) {
				applicationService.addErrorMessage("eventForm:startDate", "Invalid start date. Enter a start date that occurs before the end date.");
				isValid = false;
			}
			
		}
	
		if (event.getStartTime() == null) {
			applicationService.addErrorMessage("eventForm:startTime", "Please enter start time.");
			isValid = false;
			timeValuesOkay = false;
		} 
		
		if (event.getEndTime() == null) {
			applicationService.addErrorMessage("eventForm:endTime", "Please enter start time.");
			isValid = false;
			timeValuesOkay = false;
		} 
		
		if (dateValuesOkay && timeValuesOkay) {
			calendar.setTime(event.getStartDate());
			calendar.set(Calendar.HOUR_OF_DAY, event.getStartTime().getHourOfDay());
			calendar.set(Calendar.MINUTE, event.getStartTime().getMinutes());
			Date sDateTime = calendar.getTime();
			if (event.getStartDate().equals(event.getEndDate())) {
				calendar.setTime(event.getEndDate());
				calendar.set(Calendar.HOUR_OF_DAY, event.getEndTime().getHourOfDay());
				calendar.set(Calendar.MINUTE, event.getEndTime().getMinutes());
				Date eDateTime = calendar.getTime();
				timeValuesOkay = sDateTime.before(eDateTime);
				if (!timeValuesOkay) {
					applicationService.addErrorMessage("eventForm:startTime", "Invalid start time. Enter a start time that occurs before the end time.");
					isValid = false;
				}
			} 
			
			calendar.setTimeInMillis(System.currentTimeMillis());
			Date cDateTime =  calendar.getTime();
			if (sDateTime.before(cDateTime)) {
				applicationService.addErrorMessage("eventForm:summary", "Invalid start date and start time. Enter a start date & time that occurs after " + SIMPLE_DATE_TIME_FORMAT.format(cDateTime));
				isValid = false;
			}
		}
		
		if (isValid) {			
			if (isCreateMode()) {
				event = eventService.createEvent(event);
				return "myEvents";
			} else {
				event = eventService.updateEvent(event);
				refreshEventDetail();
				return "editEventDetail"; 
			}
		} else {
			return null;
		}
	}
	
	/**
	 * Cancel creating or modifying event
	 * @return
	 */
	public String cancelEvent() {
		if(isCreateMode()) {
			event = new Event();
			return "myEvents";
		} else {
			event = eventService.findEvent(event.getId());
			return "editEventDetail";
		}
	}
	
	
	/**
	 * Remove position from editEvent.jsp
	 * @return
	 */
	public String removePosition() {
		String positionId = applicationService.getRequestParameter("positionId");
		try {
			eventService.deletePositionOfEvent(Long.parseLong(positionId));
			refreshEventDetail();
		} catch (NumberFormatException e) {
			applicationService.addErrorMessage("Could not remove position. " + e.getMessage());
			return null;
		}
		return "editEventDetail";
	}
	
	/**
	 * Remove volunteer from a position from editEvent.jsp
	 * @return
	 */
	public String removePositionVolunteer() {
		String positionId = applicationService.getRequestParameter("positionId");
		String positionVolunteerId = applicationService.getRequestParameter("positionVolunteerId");
		try {
			User volunteer = userService.findUserAccount(Long.parseLong(positionVolunteerId));
			Position position = eventService.findPosition(Long.parseLong(positionId));
			volunteerService.unregisterForEventByPosition(position, volunteer);
			refreshEventDetail();
		} catch (NumberFormatException e) {
			applicationService.addErrorMessage("Could not remove volunteer. " + e.getMessage());
			return null;
		}
		return "editEventDetail";
	}
	
	/**
	 * Remove volunteer from a timeslot from editEvent.jsp
	 * @return
	 */
	public String removeTimeSlotVolunteer() {
		String timeSlotId = applicationService.getRequestParameter("timeSlotId");
		String timeSlotVolunteerId = applicationService.getRequestParameter("timeSlotVolunteerId");
		try {
			User volunteer = userService.findUserAccount(Long.parseLong(timeSlotVolunteerId));
			TimeSlot timeSlot = eventService.findTimeSlot(Long.parseLong(timeSlotId));
			volunteerService.unregisterForEventByTimeSlot(timeSlot, volunteer);
			refreshEventDetail();
		} catch (NumberFormatException e) {
			applicationService.addErrorMessage("Could not remove volunteer. " + e.getMessage());
			return null;
		}
		return "editEventDetail";
	}	
	
	/**
	 * Remove time slot from editEvent.jsp
	 * @return
	 */
	public String removeTimeSlot() {
		String timeSlotId = applicationService.getRequestParameter("timeSlotId");
		try {
			eventService.deleteTimeSlotOfPosition(Long.parseLong(timeSlotId));
			refreshEventDetail();
		} catch (NumberFormatException e) {
			applicationService.addErrorMessage("Could not remove time slot. " + e.getMessage());
			return null;
		}
		return "editEventDetail";
	}
	
	public ApplicationService getApplicationService() {
		return applicationService;
	}
	public void setApplicationService(ApplicationService applicationService) {
		this.applicationService = applicationService;
	}
	public UserService getUserService() {
		return userService;
	}
	public void setUserService(UserService userService) {
		this.userService = userService;
	}
	public EventService getEventService() {
		return eventService;
	}
	public void setEventService(EventService eventService) {
		this.eventService = eventService;
	}
	public User getUser() {
		return user;
	}
	public void setUser(User user) {
		this.user = user;
	}
	public Event getEvent() {
		return event;
	}
	public void setEvent(Event event) {
		this.event = event;
	}

	public String getCurrTimeZone() {
		return currTimeZone;
	}

	public void setCurrTimeZone(String currTimeZone) {
		this.currTimeZone = currTimeZone;
	}

	public boolean isCreateMode() {
		return (event.getId() == null);
	}

	public VolunteerService getVolunteerService() {
		return volunteerService;
	}

	public void setVolunteerService(VolunteerService volunteerService) {
		this.volunteerService = volunteerService;
	}

	public SortedMap<Long, List<FilledVolunteerRequisition>> getMapOfPositionVolunteers() {
		return mapOfPositionVolunteers;
	}

	public void setMapOfPositionVolunteers(
			SortedMap<Long, List<FilledVolunteerRequisition>> mapOfPositionVolunteers) {
		this.mapOfPositionVolunteers = mapOfPositionVolunteers;
	}

	public SortedMap<Long, List<FilledVolunteerRequisition>> getMapOfTimeSlotVolunteers() {
		return mapOfTimeSlotVolunteers;
	}

	public void setMapOfTimeSlotVolunteers(
			SortedMap<Long, List<FilledVolunteerRequisition>> mapOfTimeSlotVolunteers) {
		this.mapOfTimeSlotVolunteers = mapOfTimeSlotVolunteers;
	}

	public Position getPosition() {
		return position;
	}

	public void setPosition(Position position) {
		this.position = position;
	}

	public TimeSlot getTimeSlot() {
		return timeSlot;
	}

	public void setTimeSlot(TimeSlot timeSlot) {
		this.timeSlot = timeSlot;
	}
	

	

}
